'use strict'

let xxtea = require('xxtea')
let uuid = require('uuid-v4')
let fs = require('fs')
let path = require('path')
let spawn = require('child_process').spawn
let crypto = require('crypto')
let encoding = require('encoding')
let devnull = require('dev-null')

let Tasks = require('./Tasks')
let toLua = require('./simpleToLua')
let toJSON = require('./simpleToJSON')
let toLuaModulePath = require('./toLuaModulePath')
let nos = require('./nos')

let version = require('./version')
let isProduction = process.env.NODE_ENV === 'production'
let timeout = process.env.BATTLE_TIMEOUT || 10 * 1000
let protocalVersion = 3
let decryptKey = '67Bv93'

let no_encrypt = true;
let no_sign = true;

let myIP = require('ip').address()

// 后端数据校验秘钥
let key = '6KCQVd7IzBPj4wfr8i6kz0x2ZIRHLxU'
let bucket = 'eva-battle-record'
let bucketURL = `http://${bucket}.nos-eastchina1.126.net`

let tmp = 'tmp'
let dirBattleData = path.join(tmp, 'battledata')
let dirPlaybackData = path.join(tmp, 'playback')

let hash = (algo, str) => {
  return crypto.createHash(algo).update(str).digest('hex')
}

let decrypt = (data, key) => {
  return xxtea.decrypt(Buffer.from(data, 'base64').toString('binary'), key)
}

let common_cb = (err, _) => {
  if (err) {
    console.error(err)
  }
}

module.exports = function (req, res) {
  let battleID, timeoutID

  let info = { version: version }

  new Tasks()
    // 解密数据
    .then((next) => {
      let encryptedData = req.body.toString()
      let data = no_encrypt ? encryptedData : decrypt(encryptedData, decryptKey);

      info.encryptedDataLength = encryptedData.length
      info.IP = req.ip
      info.IPs = req.ips
      info.myIP = myIP

      if (data) {
        next(null, data)
      } else {
        next('DECRYPT ERROR')
      }
    })
    // 解析数据
    .then((next, data) => {
      // 一定要转码，不然中文会乱码
      data = JSON.parse(encoding.convert(data, 'ISO-8859-1', 'UTF-8'))
      if (data && data.sign) {
        next(null, data)
      } else {
        next('DATA ERROR')
      }
    })
    // 数据校验
    .then((next, data) => {
      let team1 = data.player1JsonTeamData
      let team2 = data.player2JsonTeamData || ''
      let timestamp = data.timestamp
      let recordID = data.recordID || ''
      let sign = data.sign
      let code = hash('md5', team1 + team2 + recordID + key + timestamp)

      info.timestamp = timestamp

      if (no_sign && code != sign) {
        console.error('SIGN NOT MATCH:', code, sign)
      }

      if (no_sign || code === sign) {
        next(null, data)
      } else {
        info.sign = sign
        info.code = code
        next('SIGN NOT MATCH')
      }
    })
    // 读取战斗信息，写入战斗数据
    .then((next, data) => {
      let playerID = data.player1ID
      let enemyID = data.player2ID
      let uid = uuid()

      battleID = playerID + '_vs_' + enemyID + '_' + uid

      data.uid = uid
      data.battleID = battleID
      data.player1TeamData = JSON.parse(data.player1JsonTeamData)
      if (data.player2JsonTeamData) {
        data.player2TeamData = JSON.parse(data.player2JsonTeamData)
      }
      delete data.player1JsonTeamData
      delete data.player2JsonTeamData
      data.sign1 = hash('md5', '1' + key + data.timestamp)
      data.sign0 = hash('md5', '0' + key + data.timestamp)
      data.playbackPath = path.resolve(path.join(dirPlaybackData, battleID + '.lua'))
      data.version = version
      data.protocalVersion = protocalVersion

      info.playerID = playerID
      info.enemyID = enemyID
      // info.battleID = battleID

      let battleDataPath = path.join(dirBattleData, battleID + '.lua')

      data.battleDataPath = battleDataPath

      fs.writeFile(battleDataPath, 'return ' + toLua(data), 'UTF-8',
        (err) => next(err, data))
    })
    // 开始战斗
    .then((next, data) => {
      let battleDataModulePath = toLuaModulePath(data.battleDataPath)

      let args = [
        '-O3',
        path.join('src', 'BackendBattle', 'main.lua'),
        battleDataModulePath
      ]

      let lua = spawn('luajit', args, { cwd: __dirname })

      info.timeStart = Date.now()
      info.timeout = timeout

      lua.stdout.pipe(devnull())
      lua.stderr.pipe(process.stdout)

      timeoutID = setTimeout(() => {
        lua.kill()
      }, timeout)

      lua.on('close', (code) => {
        clearTimeout(timeoutID)

        info.timeEnd = Date.now()
        info.duration = info.timeEnd - info.timeStart
        info.exitcode = code

        if (code === 0) {
          next(null, data)
        } else {
          next(`EXIT WITH CODE: ${code}`)
        }
      })
    })
    // 读取战报
    .then((next, data) => {
      fs.readFile(data.playbackPath, (err, playbackData) => next(err, data, playbackData))
    })
    // 解析战报，添加校验码
    .then((next, data, playbackData) => {
      let json = JSON.parse(toJSON(playbackData.toString()))
      let battleInfo = json.battleInfo
      if (data.challengeTimestamp) {
        let verstr = data.levelID.toString() +
          '1' +
          battleInfo.numKills +
          battleInfo.numDamageToBoss +
          battleInfo.timeElapse +
          key +
          data.challengeTimestamp
        json.challengeSign = hash('md5', verstr)
      }
      // console.log(battleInfo)
      next(null, data, json)
    })
    // 录像数据地址
    .then((next, data, json) => {
      // TODO 这里要特殊处理下生存挑战，给生存挑战生成recordID以便保存战报
      if (data.levelID == 17001) {
        data.recordID = data.uid
      }
      if (data.recordID) {
        let recordFileName = `${data.recordID}_${data.player1ID}_vs_${data.player2ID}.lua`
        let recordFilePath = path.join(dirPlaybackData, recordFileName)

        data.recordFileName = recordFileName
        data.recordFilePath = recordFilePath

        data.recordDataURL = path.join(bucketURL, recordFileName)
        json.recordDataURL = data.recordDataURL
        info.recordDataURL = data.recordDataURL
      }
      next(null, data, json)
    })
    // 把数据发送给客户端
    .then((next, data, json) => {
      res.set('Content-Type', 'text/plain')
      res.send('return ' + toLua(json))

      next(null, data, json)
    })
    // 写入录像数据
    .then((next, data, json) => {
      if (data.recordID) {
        let recordData = {
          teamData: [data.player1TeamData, data.player2TeamData],
          resultData: json
        }
        fs.writeFile(data.recordFilePath, 'return ' + toLua(recordData), (err) => next(err, data, json))
      } else {
        next(null, data, json)
      }
    })
    // 把战报存到网易NOS
    .then((next, data, json) => {
      if (data.recordID) {
        let recordFileName = data.recordFileName
        let recordFilePath = data.recordFilePath

        try {
          nos.put_file({
            bucket: bucket,
            key: recordFileName,
            contentType: 'text/plain',
            filepath: recordFilePath
          }, (result) => {
            next(null, data, json)
          })
        } catch (err) {
          console.log(`[WARNING] Error put nos object, bucket: ${bucket}, path: ${recordFilePath}, myIP: ${myIP}, error: ${JSON.stringify(err)}`)
          next(null, data, json)
        }
      } else {
        next(null, data, json)
      }
    })
    // 记录一些战斗信息
    .then((next, data, json) => {
      if (json.battleInfo) {
        info.battleInfo = Object.assign({}, json.battleInfo)
        delete info.battleInfo.sync
        delete info.battleInfo.timeStarted
      }
      next(null, data, json)
    })
    // 清理数据
    .then((next, data, playbackData) => {
      if (isProduction) {
        fs.unlink(data.playbackPath, common_cb)
        fs.unlink(data.battleDataPath, common_cb)
        if (data.recordFilePath) {
          fs.unlink(data.recordFilePath, common_cb)
        }
      }

      next(null, data)
    })
    .on('error', (err) => {
      info.error = err
      info.final = 'ERROR'

      res.sendStatus(403)

      console.log(JSON.stringify(info))
    })
    .on('done', () => {
      info.final = 'COMPLETE'
      console.log(JSON.stringify(info))
    })
    .next()
}
